/*==============================================================================
 * AUTH: Matthew Baum, 9753095
 * DATE: February-11-2012
 * EDIT: March-08-2012
 * DESC: Represents the Task Data table
 *==============================================================================*/

package SOEN_Phase_3;

// Import Java File handling libraries
import java.io.*;

// Import Java XML Parsing Libraries
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;

// Import W3C (WWW Consortium) XML DOM Libraries
import javax.xml.transform.TransformerException;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

// Date/time libraries
import java.util.Calendar;
import java.text.DateFormat;
import java.text.SimpleDateFormat;

// Java HashTable to hold Task objects
import java.util.HashMap;
import java.util.Collection;
import java.util.Iterator;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

// ArrayList
import java.util.ArrayList;

// Swing tables
import javax.swing.table.*;

//Regular expressions for pattern matching
import java.util.regex.*;


public class TaskTable extends AbstractTableModel
{

    // Hash Table to hold task objects
    private HashMap<Integer, Task> tasks;

    // Column names for the table
    static final String[] columnNames = {"ID", "Title", "Duration", "Subtasks", "Assignee ID", "Hours", "Done", "Dependencies"};

    // Variables to hold information about modified cell
    private int modifiedRow = 0;
    private int modifiedCol = 0;
    private int modifiedRowID = 0;
    private String newValue = "";

    // Constructor
    public TaskTable(String taskTableFileName)
    {
        // Initialize from Task Data file
        initializeFromFile(taskTableFileName);
    }

    // IMPLEMENTATION from TableModel
    public int getColumnCount() {
        return columnNames.length;
    }

    // IMPLEMENTATION from TableModel
    public int getRowCount() {
        return tasks.size();
    }

    // IMPLEMENTATION from TableModel
    @Override
    public String getColumnName(int col) {
        return columnNames[col];
    }

    // IMPLEMENTATION: Get class of each object
    @Override
    public Class getColumnClass(int c)
    {
        // Get indexable collection
        Task[] taskArray = new Task[tasks.size()];
        taskArray = tasks.values().toArray(taskArray);

        // Get first Task
        Task task = taskArray[0];

        // Return value
        Class returnVal = null;

        // SWITCH based on column #
        switch(c)
        {
            // ID
            case 0:
                returnVal = Integer.class;
                break;

            // Title
            case 1:
                returnVal = task.getTitle().getClass();
                break;

            // Duration
            case 2:
                returnVal = Integer.class;
                break;

            // Subtasks
            case 3:
                returnVal = String.class;
                break;

            // Assignee (string)
            case 4:
                returnVal = task.getAssignee().getClass();
                break;

            // Hours
            case 5:
                returnVal = Integer.class;
                break;

            // Done
            case 6:
                returnVal = Boolean.class;
                break;

            //Dependencies
            case 7:
        		returnVal = String.class;
        		break;
        }

        return returnVal;
    }

    // IMPLEMENTATION from TableModel
    public Object getValueAt(int row, int col)
    {
        // Get indexable collection
        Task[] taskArray = new Task[tasks.size()];
        taskArray = tasks.values().toArray(taskArray);

        // Get task based on row number
        Task task = taskArray[row];

        // Return value
        Object returnVal = null;

        // SWITCH based on column #
        switch(col)
        {
            // ID
            case 0:
                returnVal = task.getID();
                break;

            // Title
            case 1:
                returnVal = task.getTitle();
                break;

            // Duration
            case 2:
                returnVal = task.getDuration();
                break;

            // Subtasks
            case 3:

                // String to hold subtask data
                String subtaskString = "";

                // Get subtasks
                ArrayList<Task> subtasks = task.getSubtasks();

                // Build subtask string
                for(int i = 0; i != subtasks.size(); ++i)
                {
                    subtaskString += subtasks.get(i).getID();

                    if(i != subtasks.size() - 1)
                    {
                        subtaskString += ", ";
                    }
                }

                returnVal = subtaskString;

                break;

            // Assignee (string)
            case 4:
            	returnVal = task.getAssigneeID();
                break;

            // Hours
            case 5:
                returnVal = task.getTimeInvested();
                break;

            // Done
            case 6:
                returnVal = task.getCompletion();
                break;

        	// Dependencies

        	case 7:

                // String to hold subtask data
                String dependencyString = "";

                // Get subtasks
                ArrayList dependencies = task.getDependencyIDs();

                // Build subtask string
                for(int i = 0; i != dependencies.size(); ++i)
                {
                    dependencyString += dependencies.get(i);

                    if(i != dependencies.size() - 1)
                    {
                        dependencyString += ", ";
                    }
                }

                returnVal = dependencyString;
        }

        return returnVal;
    }

    // Initialize the table from an XML file
    public final void initializeFromFile(String taskTableFileName)
    {
    
    		//Initialize file and create a blank one if needed
		File taskTableFile = new File(taskTableFileName);

		if(!taskTableFile.exists()){
			try
			{
				FileWriter fstream = new FileWriter(taskTableFile);
  				BufferedWriter out = new BufferedWriter(fstream);
 				out.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<task_table>\n</task_table>");
  				out.close();
			}
			catch (IOException e)
			{
				System.out.println("Unknown error closing");
			}
		}
    
        // Factory design pattern to produce DocumentBuilders:
		DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();

		try
		{
			// Use factory to generate a new document builder:
			DocumentBuilder builder = docBuilderFactory.newDocumentBuilder();

			// Create a Document object by parsing the XML document:
			Document document = builder.parse (taskTableFile);

			// Get all task items:
			NodeList taskList = document.getElementsByTagName("task");

			// INIT the task object array:
			tasks = new HashMap<Integer, Task>( taskList.getLength() * 2);


      //Process tasks iff there are tasks to process
      if (taskList.getLength() !=0)
           // Process task items:
           for(int i = 0; i != taskList.getLength(); ++i)
           {
              // Get a task item:
              Node task = taskList.item(i);

              // Create a Task object from the XML DOM node:
              Task newTask = Task.getTaskFromXMLNode(task);

              // Add task to table
              tasks.put( newTask.getID(), newTask  );

              // Check for subtasks and add to table
              addSubtasks(newTask);
             }

            // Generate task dependencies
            Task[] taskVals = new Task[tasks.size()];
            taskVals = tasks.values().toArray(taskVals);
            ArrayList<Integer> depIds;

            // Iterate over tasks
            for(int i = 0; i != taskVals.length; ++i)
            {
                depIds = taskVals[i].getDependencyIDs();

                // Iterate over task dependency IDs
                for(int j = 0; j != depIds.size(); ++j)
                {
                    // Add the task with the corresponding ID to dependencies
                    taskVals[i].addDependency( tasks.get( depIds.get(j) ) );
                }
            }
		}
     catch (FileNotFoundException e)
		{
			System.out.println("Critical error, no tasks.xml found");
			System.exit(0);
		}  
		catch (ParserConfigurationException PCe)
		{
			System.out.println("An exception occured in configuring the XML parser!");
			PCe.printStackTrace(System.out);
		}
		catch (IOException IOe)
		{
			System.out.println("An Input/Output Exception occured while parsing an xml document!");
			IOe.printStackTrace(System.out);
		}
                catch(SAXParseException SPe)
                {
                    	System.out.println("PARSING ERROR: Input file may be empty!");
			SPe.printStackTrace(System.out);
                }
                catch(SAXException Se)
                {
                        System.out.println("XML API ERROR: An error has occurred in building an XML document object!");
			Se.printStackTrace(System.out);
                }
		catch (Exception e)
		{
			System.out.println("AN UNRECOGNIZED ERROR HAS OCCURED!");
			e.printStackTrace(System.out);
		}
    }

    // Matt: Private helper method to add subtasks (from a given Task) to the table
    private void addSubtasks(Task task)
    {
        // Get Subtasks from Task
        ArrayList<Task> subtasks = task.getSubtasks();

        // Temp var to hold subtasks
        Task subtask;

        // IF there are any subtasks, add them
        if( !subtasks.isEmpty() )
        {
            // Iterate over subtasks
            for(int i = 0; i != subtasks.size(); ++i)
            {
                // Get subtask
                subtask = subtasks.get(i);

                // Add to table
                tasks.put(subtask.getID(), subtask);

                // IF this subtask has subtasks, make a recursive call
                if(!subtask.getSubtasks().isEmpty())
                {
                    addSubtasks(subtask);
                }
            }
        }
    }

    // Write the table to a text file
    public void writeToTXT(String dir, String fName)
    {
        // Write data to a TXT file
        printTaskTable( dir, fName );
    }

    /*
     * Print a text file representation of a Task Table
     * to a specified directory.
     */
    private void printTaskTable(String dir, String fName)
    {
        // Get today's date: yyyy-MM-dd_HH-mm-ss
        DateFormat prettyFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Calendar cal = Calendar.getInstance();
        String prettyDate = prettyFormat.format( cal.getTime() );

        // Create a File object
        File outputFile = new File(dir, fName);

        // Calculate Table column WIDTH
        int[] columnWidths = calculateTaskTableColumnWidths();

        // Calculate total table WIDTH
        int tableWidth = calculateTaskTableWidth(columnWidths);

        try
        {
            // Create Ouput file writer
            FileWriter fw = new FileWriter(outputFile);

            // Write a title
            fw.write("\nTASKS TABLE:\n\n");

            // Write out table header
            fw.write( printTaskTableHeading(columnWidths) );

            // Write out table rows
            // Get collection of Tasks
            Collection<Task> taskObjects = tasks.values();
            Iterator iter = taskObjects.iterator();
            Task currentTask;

            while(iter.hasNext() )
            {
                // Get the current task
                currentTask = (Task)iter.next();
                fw.write( printTaskTableRow(columnWidths, currentTask) );
            }

            // Write out table footer
            fw.write( printTaskTableFooter(tableWidth, prettyDate) );

            // Close file writer
            fw.close();
        }
        catch( IOException IOe )
        {
            System.out.println("File Output Error!");
			IOe.printStackTrace(System.out);
        }
    }

    /*
     * Calculate the width of each column for the output table, also modify
     * input integer to store total width
     */
    private int[] calculateTaskTableColumnWidths()
    {
        /*
         * Create array to hold column widths
         * Initial column values = column titles + 2 * CONST_TAB (each side).
         *
         * COLUMN NAMES:
         * - ID
         * - TITLE
         * - SUBTASKS (assume constant width)
         * - DUR (hrs)
         * - ASSIGNED TO
         * - HOURS
         * - DONE (this column has a fixed width)
         * - DEPENDENCIES
         *
         */
        int padding = DataHelpers.CONST_TAB_WIDTH * 2;
        int[] columnWidths = { 2, 5, 8, 9, 11, 5, 4, 12 };

        /*
         * Loop through the array of all tasks, and find the max length of each
         * column
         */

        // Get collection of Tasks
        Collection<Task> taskObjects = tasks.values();
        Iterator iter = taskObjects.iterator();
        Task currentTask;

        while(iter.hasNext() )
        {
            // Get the current task
            currentTask = (Task)iter.next();

            // Check Task ID
            if(Integer.toString(currentTask.getID() ).length() > columnWidths[0])
            {
                columnWidths[0] = Integer.toString(currentTask.getID() ).length();
            }

            // Check Task Title
            if(currentTask.getTitle().length() > columnWidths[1])
            {
                columnWidths[1] = currentTask.getTitle().length();
            }

            // Check Task Duration
            if(Integer.toString(currentTask.getDuration() ).length() > columnWidths[2])
            {
                columnWidths[3] = Integer.toString(currentTask.getDuration() ).length();
            }

            // Check Task Assignee
            if(currentTask.getAssignee().length() > columnWidths[3])
            {
                columnWidths[4] = currentTask.getAssignee().length() +
                        Integer.toString(currentTask.getAssigneeID() ).length() + 6;
            }

            // Check Time Invested
            if(Integer.toString(currentTask.getTimeInvested() ).length() > columnWidths[4])
            {
                columnWidths[5] = Integer.toString(currentTask.getTimeInvested() ).length();
            }


            // NOTE: the DONE column is of constant width
        }

        // Add PADDING to column widths, and calculate total width
        for(int i = 0; i != columnWidths.length; ++i)
        {
            columnWidths[i] += padding;
        }

        // Return the calculated widths
        return columnWidths;
    }

    // Calculate total table width
    private int calculateTaskTableWidth(int[] columnWidths)
    {
        int totalWidth = 0;

        // Add all column widths
        for(int i = 0; i!= columnWidths.length; ++i)
        {
            totalWidth += columnWidths[i];
        }

        // Add space for column separators ( '|' )
        totalWidth += columnWidths.length + 1;

        return totalWidth;
    }

    // Print a heading for the task table
    private String printTaskTableHeading(int[] columnWidths)
    {
        String header = "|";

        // Fill the TOP of the header
        for(int i = 0; i != columnWidths.length; ++i)
        {
            header += DataHelpers.printChars('=', columnWidths[i]) + '|';
        }

        // FILL HEADER CONTENT
        header += "\n|";
        header += DataHelpers.padString("ID", columnWidths[0]) + '|';
        header += DataHelpers.padString("TITLE", columnWidths[1]) + '|';
        header += DataHelpers.padString("SUBTASKS", columnWidths[2]) + '|';
        header += DataHelpers.padString("DUR (hrs)", columnWidths[3]) + '|';
        header += DataHelpers.padString("ASSIGNED TO", columnWidths[4]) + '|';
        header += DataHelpers.padString("HOURS", columnWidths[5]) + '|';
        header += DataHelpers.padString("DONE", columnWidths[6]) + '|';
        header += DataHelpers.padString("DEPENDENCIES", columnWidths[7]) + '|';
        header += "\n|";

        // Fill the BOTTOM of the header
        for(int i = 0; i != columnWidths.length; ++i)
        {
            header += DataHelpers.printChars('=', columnWidths[i]) + '|';
        }

        return( header );
    }

    // Print a row in the task table
    private String printTaskTableRow(int[] columnWidths, Task task)
    {
        String entry = "\n|";

        // FILL ENTRY CONTENT
        entry += DataHelpers.padString(Integer.toString(task.getID() ), columnWidths[0]) + '|';
        entry += DataHelpers.padString(task.getTitle(), columnWidths[1]) + '|';

        // Print the first subtask
        if(task.getSubtasks().size() > 0)
        {
            entry += DataHelpers.padString(Integer.toString(task.getSubtasks().get(0).getID() ), columnWidths[2]) + '|';
        }

        // IF there are no subtasks, print a "line"
        else
        {
            entry += DataHelpers.padString("--", columnWidths[2]) + '|';
        }

        entry += DataHelpers.padString(Integer.toString(task.getDuration() ), columnWidths[3]) + '|';
        entry += DataHelpers.padString(task.getAssignee() + " (ID " + task.getAssigneeID() + ')', columnWidths[4]) + '|';
        entry += DataHelpers.padString(Integer.toString(task.getTimeInvested() ), columnWidths[5]) + '|';

        if(task.getCompletion() )
        {
            entry += DataHelpers.padString("YES", columnWidths[6]) + '|';
        }
        else
        {
            entry += DataHelpers.padString("NO", columnWidths[6]) + '|';
        }

        // Print all other subtasks (there may be none)
        if(task.getSubtasks().size() > 1)
        {
            for(int i = 1; i != task.getSubtasks().size(); ++i)
            {
                // Go to next line
                entry += "\n|";

                // Fill leading columns with empty spaces
                entry += DataHelpers.printChars(' ', columnWidths[0]) + '|';
                entry += DataHelpers.printChars(' ', columnWidths[1]) + '|';

                // Fill in the task number
                entry += DataHelpers.padString(Integer.toString(task.getSubtasks().get(i).getID() ), columnWidths[2]) + '|';

                // Fill foloowing columns with empty spaces
                entry += DataHelpers.printChars(' ', columnWidths[3]) + '|';
                entry += DataHelpers.printChars(' ', columnWidths[4]) + '|';
                entry += DataHelpers.printChars(' ', columnWidths[5]) + '|';
                entry += DataHelpers.printChars(' ', columnWidths[6]) + '|';
            }
        }

        entry += "\n|";

        // Draw a line at the bottom
        for(int i = 0; i != columnWidths.length; ++i)
        {
            entry += DataHelpers.printChars('-', columnWidths[i]) + '|';
        }

        return( entry );
    }

    // Print a footer for the task table
    private String printTaskTableFooter(int tableWidth, String date)
    {
        String footer = "\n|";
        String footerComment = "*** END OF TABLE : " + date + " ***";

        // Fill the TOP of the footer
        footer += DataHelpers.printChars(' ', tableWidth - 2) + '|';

        // FILL HEADER CONTENT
        int leftPad = (int)(tableWidth - footerComment.length() - 2) / 2;
        int rightPad = tableWidth - footerComment.length() - 2 - leftPad;

        footer += "\n|" +
                DataHelpers.printChars(' ', leftPad) +
                footerComment +
                DataHelpers.printChars(' ', rightPad) +
                "|\n|";

        // Fill the BOTTOM of the header
        footer += DataHelpers.printChars('=', tableWidth - 2) + '|';

        return( footer );
    }

    // Return array of all Tasks
    public Object[] getTasks()
    {
        return tasks.values().toArray();
    }


    // Get a specific task by ID
    public Task getTaskByID(int ID)
    {
        return tasks.get(ID);
    }

    // Modify a given task's ID
    public boolean modifyTaskID(int ID, int newID)
    {
        // Get the task object
    	Task task = tasks.get(ID);

        // Success result
        boolean success = false;

        // IF it is not NULL (task exists)
        if(newID != ID)
        {
        	if(task != null)
	        {
	            // Verify that the newID is not yet in use
	            if(! tasks.containsKey(newID) )
	            {
	                // Set the new ID
	                task.setID(newID);

	                // Set success
	                success = true;
	            }
	        }
        }


        // Return success result
        return success;
    }

    // Modify a given task's name
    public void modifyTaskTitle(int ID, String newTitle)
    {
        // Get the task object

        Task task = tasks.get(ID);

        // IF it is not NULL, cast it
        if(task != null)
        {
            // Set new name
            task.setTitle(newTitle);
        }
    }

    // Modify task's estimated duration
    public int modifyEstimatedDuration(int ID, int duration)
    {
        // Get the task
        Task task = tasks.get(ID);

        // Integer result (assignee ID OR -1)
        int assigneeID = -1;

        // IF the task exists, modify
        if(task != null)
        {
            task.setDuration(duration);
            assigneeID = task.getAssigneeID();
        }

        return assigneeID;
    }

    // Modify task's assignee name
    public void modifyAssignee(int ID, String assigneeIDStr)
    {
        // Get the task
        Task task = tasks.get(ID);
        int assigneeID = Integer.parseInt(assigneeIDStr);

        // IF the task exists, modify
        if(task != null)
        {
            //task.setAssignee(assignee);
            task.setAssigneeID(assigneeID);
        }
    }

    // Modify task's completion status
    public void modifyCompletion(int ID, boolean completion)
    {
        // Get the task
        Task task = tasks.get(ID);

        // IF the task exists, modify
        if(task != null)
        {
            task.setCompletion(completion);
        }
    }



	//modify subtasks treating all non numerical characters as splitters
	public void modifySubTasks(int ID, String x){
		tasks.get(ID).clearSubTasks();

		Pattern p = Pattern.compile("[\\D]+");

		if ( !(x.equals("") ) )
		{

			String[] possibleSubTasks = p.split(x);

			Task task = tasks.get(ID);

			for(String subTask : possibleSubTasks)
			{

				int temp = Integer.parseInt(subTask);

				if (taskExists(temp))
				{
					task.addSubtask(tasks.get(temp));
				}
			}
		}

		tasks.get(ID).checkDependencies();
	}

	//modify dependencies treating all non numerical characters as splitters
	public void modifyDependencies(int ID, String x){
		tasks.get(ID).clearDependencies();


		Pattern p = Pattern.compile("[\\D]+");

		if ( !(x.equals("") ) )
		{
			String[] possibleDependencies = p.split(x);
			Task task = tasks.get(ID);

			for(String dep : possibleDependencies)
			{
				int temp = Integer.parseInt(dep);
				if (taskExists(temp))
				{
				task.addDependencyID(temp);
				}
			}
		}
	}


    // Update time invested on a task
    public void modifyTimeInvested(int ID, int timeToAdd)
    {
        // Get the task
        Task task = tasks.get(ID);

        // IF the task exists, modify
        if(task != null)
        {
            task.addTimeInvested(timeToAdd);
        }
    }

    // Save Task Table to XML file
    public void saveToXML(String fname)
    {
        try
        {
            DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

            // Create Document type
            Document doc = docBuilder.newDocument();

            // Create the root element
            Element rootElement = doc.createElement("task_table");

            // Get the collection of tasks
            Collection<Task> taskObjects = tasks.values();
            Iterator iter = taskObjects.iterator();
            Task nextTask;

            // While there is more task
            while (iter.hasNext())
            {

                // Get the next task
                nextTask = (Task)iter.next();

                // IF this task is NOT a subtask:
                if(!nextTask.isSubtask())
                {
                    // Create the task element
                    Element taskElement = nextTask.toXML(doc, false);

                    rootElement.appendChild(taskElement);
                }
            }

            doc.appendChild(rootElement);

            // write to xml file
            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            Transformer transformer = transformerFactory.newTransformer();

            //set output properties to allow for proper indentation in the xml file
			transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
		    transformer.setOutputProperty(OutputKeys.METHOD, "xml");
		    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		    transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
		    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

            DOMSource source = new DOMSource(doc);
            StreamResult result = new StreamResult(new File( fname ));

            transformer.transform(source, result);
        }
        catch (ParserConfigurationException pce)
        {
            pce.printStackTrace(System.out);
        }
        catch (TransformerException tfe)
        {
            tfe.printStackTrace(System.out);
        }
    }

    // Add a new Task to the table
    public void addNewTask(int ID, String title, int duration, int aID, String aName)
    {
        tasks.put(ID, new Task(ID, duration, title, aName, aID, 0, false, new ArrayList<Task>(), new ArrayList()  ) ) ;
    }

    // Delete a Task from the table
    public Task deleteTask(int ID)
    {
        // Task to be deleted
        Task deletedTask = tasks.remove(ID);

        // IF the task existed
        if(deletedTask != null)
        {
            // IF the task was a subtask: remove it from its parent
            removeSubtaskFromParent(deletedTask);
        }

        // Return result
        return deletedTask;
    }

    // Private helper: Remove a given Task from its parent
    private void removeSubtaskFromParent(Task subtask)
    {
        subtask.removeFromParent();
    }

    // Task exists?
    public boolean taskExists(int ID)
    {
        return tasks.containsKey(ID);
    }

    // Return the number of modified row
    public int getModifiedRow()
    {
        return modifiedRow;
    }

    // Return the number of modified column
    public int getModifiedCol()
    {
        return modifiedCol;
    }

    // Return the ID of modified row
    public int getModifiedRowID()
    {
        return modifiedRowID;
    }

    // Return the new value
    public String getNewValue()
    {
        return newValue;
    }

    // Store values
    @Override
    public void setValueAt(Object value, int row, int col)
    {
        modifiedRow = row;

        modifiedCol = col;

        modifiedRowID = Integer.parseInt(getValueAt(row,0).toString());

        newValue = value.toString();
    }

    // Make all cells editable
    @Override
    public boolean isCellEditable(int row, int col)
    {
        if (col == 0)
        {
            return false;
        }
        else
        {
            return true;
        }
    }

}
